package com.yueyang.sojourn.server.common.baymax.web.resolver;

import com.yueyang.sojourn.server.common.baymax.web.result.RestResult.Codes;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * 捕捉配置路径上所有请求异常，并转换为标准错误输出
 * 
 * @author huisman
 */
public class CatchAllExcetpionResolver extends AbstractResultExceptionResolver {
  /**
   * 我们支持的类型
   */
  private final Set<DispatcherType> supportDispatchTypes =
      new HashSet<>(Arrays.asList(DispatcherType.REQUEST, DispatcherType.ASYNC));

  /**
   * Spring MVC异常类对应的http 状态码
   */
  Map<String, Integer> exceptionHttpCodeMap = new HashMap<>();
  private final boolean logErrorTrace;

  /**
   * 优先级要比spring自己配置的要高
   * 
   * @since: 1.0.10
   * @return
   */
  @Override
  public int getOrder() {
    return HIGHEST_PRECEDENCE + 20000000;
  }

  public CatchAllExcetpionResolver(boolean logErrorTrace) {
    // 设置异常和http状态码对应关系
    loadingExceptionCodeMapping();
    this.logErrorTrace = logErrorTrace;
    logger.warn("=====>> 所有请求（仅限使用@ResponseBody标注的方法）异常将转换为标准响应json");
  }

  /**
   * 设置异常和http状态码对应关系
   * 
   * @author huisman
   * @version v1
   */
  private void loadingExceptionCodeMapping() {
    Properties properties = new Properties();
    // 绝对路径开始
    try (InputStream in = this.getClass()
        .getResourceAsStream("/spring-mvc-exception-httpstatus-mapping.properties")) {
      properties.load(in);
      Enumeration<Object> keys = properties.keys();
      while (keys.hasMoreElements()) {
        String ex = (String) keys.nextElement();
        this.exceptionHttpCodeMap.put(ex,
            Integer.parseInt(String.valueOf(properties.getProperty(ex))));

      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  protected boolean shouldResolveException(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) {
    if (ex == null) {
      return false;
    }
    // dispatch
    if (!supportDispatchTypes.contains(request.getDispatcherType())) {
      return false;
    }
    if (RestResponseMessageProviders.support(ex)) {
      return false;
    }

    if (handler == null) {
      // 没找到handler时。我们也拦截，比如请求方法不正确。。等等。
      return true;
    }
    // 处理HandlerMethod，仅限Rest接口
    if (handler instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      MethodParameter returnType = handlerMethod.getReturnType();
      if (AnnotationUtils.findAnnotation(returnType.getContainingClass(),
          ResponseBody.class) == null
          && returnType.getMethodAnnotation(ResponseBody.class) == null) {
        return false;
      }
    }
    return true;
  }

  /*
   * @see
   * org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#logException(java.lang
   * .Exception, javax.servlet.http.HttpServletRequest)
   */
  @Override
  protected void logException(Exception ex, HttpServletRequest request) {
    // 如果是SpringMVC内部异常，则没必要打印堆栈
    if (this.logErrorTrace && !isSpringInternalException(ex)) {
      this.logger.error(formatLogMessage(false, ex, request), ex);
    } else {
      this.logger.error(formatLogMessage(true, ex, request));
    }
  }

  private boolean isSpringInternalException(Exception ex) {
    if (ex != null && this.exceptionHttpCodeMap.get(ex.getClass().getName()) != null) {
      return true;
    }
    return false;
  }

  @Override
  protected com.yueyang.sojourn.server.common.baymax.web.resolver.RestResponseMessage resolveResponseMessage(HttpServletRequest request,
                                                                                           HttpServletResponse response, Object handler, Exception ex) {
    int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
    // 首先查找是否是SpringMVC抛出的异常
    Integer mvcExceptionHttpCode = this.exceptionHttpCodeMap.get(ex.getClass().getName());
    String responseReason = null;
    if (mvcExceptionHttpCode != null) {
      statusCode = mvcExceptionHttpCode;
    } else {
      ResponseStatus responseStatus =
          AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
      if (responseStatus != null) {
        statusCode = responseStatus.value().value();
        responseReason = responseStatus.reason();
      }
    }
    if (!StringUtils.hasText(responseReason)) {
      if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
        responseReason = "系统内部异常";
      }else {
        responseReason = ex.getMessage();
      }
    }
    // 如果异常没有抛出message，则默认返回异常类
    if (!StringUtils.hasText(responseReason)) {
      responseReason = ex.getClass().getName();
    }
    return new RestResponseMessage(Codes.UNKNOWN, responseReason, statusCode);
  }

}


